1   // Copyright 2008, 2009, 2010, 2011 The Apache Software Foundation
2   //
3   // Licensed under the Apache License, Version 2.0 (the "License");
4   // you may not use this file except in compliance with the License.
5   // You may obtain a copy of the License at
6   //
7   // http://www.apache.org/licenses/LICENSE-2.0
8   //
9   // Unless required by applicable law or agreed to in writing, software
10  // distributed under the License is distributed on an "AS IS" BASIS,
11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  // See the License for the specific language governing permissions and
13  // limitations under the License.
14  
15  package org.apache.tapestry5.internal.transform;
16  
17  import java.util.Collections;
18  import java.util.Comparator;
19  import java.util.LinkedList;
20  import java.util.List;
21  import java.util.Set;
22  
23  import org.apache.tapestry5.EventConstants;
24  import org.apache.tapestry5.annotations.PageActivationContext;
25  import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
26  import org.apache.tapestry5.ioc.internal.util.InternalUtils;
27  import org.apache.tapestry5.model.MutableComponentModel;
28  import org.apache.tapestry5.plastic.FieldHandle;
29  import org.apache.tapestry5.plastic.PlasticClass;
30  import org.apache.tapestry5.plastic.PlasticField;
31  import org.apache.tapestry5.runtime.Component;
32  import org.apache.tapestry5.runtime.ComponentEvent;
33  import org.apache.tapestry5.services.ComponentEventHandler;
34  import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
35  import org.apache.tapestry5.services.transform.TransformationSupport;
36  
37  /**
38   * Provides the page activation context handlers.
39   *
40   * @see org.apache.tapestry5.annotations.PageActivationContext
41   */
42  public class PageActivationContextWorker implements ComponentClassTransformWorker2
43  {
44      private static final Comparator<PlasticField> INDEX_COMPARATOR = new Comparator<PlasticField>()
45      {
46          public int compare(PlasticField field1, PlasticField field2) {
47              int index1 = field1.getAnnotation(PageActivationContext.class).index();
48              int index2 = field2.getAnnotation(PageActivationContext.class).index();
49  
50              int compare = index1 < index2 ? -1 : (index1 > index2 ? 1 : 0);
51              if (compare == 0)
52              {
53                  compare = field1.getName().compareTo(field2.getName());
54              }
55              return compare;
56          }
57      };
58  
59      public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model)
60      {
61          List<PlasticField> fields = plasticClass.getFieldsWithAnnotation(PageActivationContext.class);
62  
63          if (!fields.isEmpty())
64          {
65              transformFields(support, fields);
66          }
67      }
68  
69      private void transformFields(TransformationSupport support, List<PlasticField> fields)
70      {
71          List<PlasticField> sortedFields = CollectionFactory.newList(fields);
72          Collections.sort(sortedFields, INDEX_COMPARATOR);
73          validateSortedFields(sortedFields);
74  
75          PlasticField firstField = sortedFields.get(0);
76          PageActivationContext firstAnnotation = firstField.getAnnotation(PageActivationContext.class);
77  
78          // these arrays reduce memory usage and allow the PlasticField instances to be garbage collected
79          FieldHandle[] handles = new FieldHandle[sortedFields.size()];
80          String[] typeNames = new String[sortedFields.size()];
81  
82          int i = 0;
83          for (PlasticField field : sortedFields) {
84              handles[i] = field.getHandle();
85              typeNames[i] = field.getTypeName();
86              ++i;
87          }
88  
89          if (firstAnnotation.activate())
90          {
91              support.addEventHandler(EventConstants.ACTIVATE, 1,
92                      "PageActivationContextWorker activate event handler", createActivationHandler(handles, typeNames));
93          }
94  
95          if (firstAnnotation.passivate())
96          {
97              support.addEventHandler(EventConstants.PASSIVATE, 0,
98                      "PageActivationContextWorker passivate event handler", createPassivateHandler(handles));
99          }
100 
101         // We don't claim the field, and other workers may even replace it with a FieldConduit.
102     }
103 
104     private void validateSortedFields(List<PlasticField> sortedFields) {
105         List<Integer> expectedIndexes = CollectionFactory.newList();
106         List<Integer> actualIndexes = CollectionFactory.newList();
107         Set<Boolean> activates = CollectionFactory.newSet();
108         Set<Boolean> passivates = CollectionFactory.newSet();
109 
110         for (int i = 0; i < sortedFields.size(); ++i) {
111             PlasticField field = sortedFields.get(i);
112             PageActivationContext annotation = field.getAnnotation(PageActivationContext.class);
113             expectedIndexes.add(i);
114             actualIndexes.add(annotation.index());
115             activates.add(annotation.activate());
116             passivates.add(annotation.passivate());
117         }
118 
119         List<String> errors = CollectionFactory.newList(); 
120         if (!expectedIndexes.equals(actualIndexes)) {
121             errors.add(String.format("Index values must start at 0 and increment by 1 (expected [%s], found [%s])", 
122                     InternalUtils.join(expectedIndexes), InternalUtils.join(actualIndexes)));
123         }
124         if (activates.size() > 1) {
125             errors.add("Illegal values for 'activate' (all fields must have the same value)");
126         }
127         if (passivates.size() > 1) {
128             errors.add("Illegal values for 'passivate' (all fields must have the same value)");
129         }
130         if (!errors.isEmpty()) {
131             throw new RuntimeException(String.format("Invalid values for @PageActivationContext: %s", InternalUtils.join(errors)));
132         }
133     }
134 
135     private static ComponentEventHandler createActivationHandler(final FieldHandle[] handles, final String[] fieldTypes)
136     {
137         return new ComponentEventHandler()
138         {
139             public void handleEvent(Component instance, ComponentEvent event)
140             {
141                 int count = Math.min(handles.length, event.getEventContext().getCount());
142                 for (int i = 0; i < count; ++i)
143                 {
144                     String fieldType = fieldTypes[i];
145                     FieldHandle handle = handles[i];
146                     Object value = event.coerceContext(i, fieldType);
147                     handle.set(instance, value);
148                 }
149             }
150         };
151     }
152 
153     private static ComponentEventHandler createPassivateHandler(final FieldHandle[] handles)
154     {
155         return new ComponentEventHandler()
156         {
157             public void handleEvent(Component instance, ComponentEvent event)
158             {
159                 Object result;
160                 if (handles.length == 1) {
161                     // simple / common case for a single @PageActivationContext
162                     result = handles[0].get(instance);
163                 } else {
164                     LinkedList<Object> list = CollectionFactory.newLinkedList();
165 
166                     // iterate backwards
167                     for (int i = handles.length - 1; i > -1; i--) {
168                         FieldHandle handle = handles[i];
169                         Object value = handle.get(instance);
170 
171                         // ignore trailing nulls
172                         if (value != null || !list.isEmpty()) {
173                             list.addFirst(value);
174                         }
175                     }
176                     result = list.isEmpty() ? null : list;
177                 }
178 
179                 event.storeResult(result);
180             }
181         };
182     }
183 }